#!/usr/bin/python
import codecs
import datetime
import json
import logging
import optparse
import os
import os.path
import re
import subprocess
import sys
import tempfile
import time
import urllib2
from xml.dom.minidom import Document

class Grabber:
    # CTOR
    def __init__(self):
        # Config file.
        self.configFile = os.path.expanduser("~/.tv_grab_py_fi/channels.conf")

        # Command line options.
        self.options = None

        # Get timezone.
        dateApp = "date"
        if sys.platform.lower().startswith("win"):
            dateApp = "C:/MinGW/msys/1.0/bin/%s.exe" % dateApp
        self.timeZone = subprocess.Popen(["date", "+%z"], stdout = subprocess.PIPE).communicate()[0].strip()

        # The count of channels.
        self.channelCount = 0

        # The current progress step.
        self.currentStep = 0

        # Channel map.
        self.channelMap = {}

        # Element tree for the output.
        self.document = Document()
        tv = self.document.createElement('tv')
        tv.setAttribute('date', datetime.datetime(1, 1, 1).utcnow().isoformat().split('.')[0])
        tv.setAttribute('generator-info-name', 'tv_grab_py_fi/1.0')
        tv.setAttribute('generator-info-url', 'http://code.google.com/p/tv-grab-py-fi')
        tv.setAttribute('source-info-url', 'http://m.telkku.com')
        tv.setAttribute('source-info-name', 'Telkku.com')
        self.document.appendChild(tv)
        self.tvNode = tv

    # Compress response string.
    def compressResponse(self, url):
        r = urllib2.urlopen(url)
        encodingToUse = r.headers['content-type'].split('=')[1].strip().lower()
        retVal = ""
        for part in r.readlines():
            retVal += part.strip()
        return unicode(retVal, encodingToUse)

    # Return a list containing urls for each program group.
    def getProgramGroupUrls(self):
        urls = []
        responseString = self.compressResponse("http://m.telkku.com")
        pat = re.compile('groupLink.+?http://m\.telkku\.com/programtable/show/(\d+)/(.+?)"', re.I);
        result = pat.findall(responseString)
        for item in result:
            urlString = "http://m.telkku.com/programtable/show/%s/%s" % (item[0], item[1])
            logging.debug(urlString)
            urls.append(urlString)
        return urls

    # Return a dictionary containing channel names and their respective urls.
    def getChannelUrls(self, url):
        logging.debug(url)
        channelMap = {}
        responseString = self.compressResponse(url)
        pat = re.compile('<\s*caption\s*>\s*<\s*a\s*href\s*=\s*"http://m.telkku.com/channel/show/(.+?)/(.+?)"\s*>(.*?)<', re.I)
        result = pat.findall(responseString)
        for item in result:
            channelMap[int(item[0])] = item[2]
        return channelMap

    # Return program description.
    def getProgramInfo(self, programUrl):
        logging.debug("Program info from : %s" % programUrl)
        responseString = self.compressResponse(programUrl)
        patTitle = re.compile('<\s*h2\s*>(.*?)<\s*/h2\s*>', re.I)
        patDescription = re.compile('<\s*p\s*class\s*=\s*"programDescription"\s*>(.*?)<\s*/p\s*>', re.I)
        titleResult = patTitle.findall(responseString)[0]
        descriptionResult = patDescription.findall(responseString)[0]
        return (programUrl.split('/')[-1], titleResult, descriptionResult)

    # Return program guide for the specified channel key code for the given date.
    def getProgramGuide(self, channelCode, targetDate):
        channelUrl = "http://m.telkku.com/channel/show/%s/%s" % (channelCode, targetDate)
        logging.debug("Programs for channel '%s' for %s:" % (self.channelMap[channelCode], targetDate))
        logging.debug("URL(%s)" % channelUrl)
        responseString = self.compressResponse(channelUrl)
        pat = re.compile('<\s*td\s*class\s*=\s*"progName"\s*>(.*?)<\s*/td\s*>', re.I)
        result = pat.findall(responseString)
        stepCount = self.channelCount * self.options.days
        if not stepCount:
            stepCount = 1
        lineOfDots = ''
        self.currentStep += 1
        for item in result:
            parts = item.split('>')
            programmeUrl = parts[0].split('"')[1]
            programmeName = parts[1].split('<')[0]
            # Print some progress stuff of screen.
            lineOfDots += '.'
            progress = 100.0 * self.currentStep / stepCount
            if not self.options.quiet:
                if len(lineOfDots) == 1 and self.currentStep > 1:
                    print()
                print >> sys.stderr, "Scraping... %5.1f%% %s\r" % (progress, lineOfDots),
            else:
                print '-',

            # Get program link and fetch start time and description.
            startTime, title, description = self.getProgramInfo(programmeUrl)

            # Tweak start time, i.e. remove channel code from it.
            startTime = ' '.join((startTime[0:startTime.rfind(channelCode)], self.timeZone))

            # Build a program info element.
            programElement = self.document.createElement('programme')
            programElement.setAttribute('start', startTime)
            programElement.setAttribute('channel', "%s.telkku.com" % channelCode)

            # Build a title element.
            titleElement = self.document.createElement('title')
            titleElementText = None
            if title is not None and len(title):
                titleElement = self.document.createTextNode(title)
            else:
                titleElement = self.document.createTextNode(programmeUrl)
            logging.debug("%s: %s" % (startTime, title))

            # Build a description element.
            descriptionElement = self.document.createElement('desc')
            descriptionElementText = None
            if description is not None and len(description):
                descriptionElementText = self.document.createTextNode(description)
            else:
                descriptionElementText = self.document.createTextNode(title)
            descriptionElement.appendChild(descriptionElementText)

            # Add elements to the tree.
            programElement.appendChild(titleElement)
            programElement.appendChild(descriptionElement)
            self.tvNode.appendChild(programElement)

    # Get the channel config.
    def loadChannelConfig(self):
        logging.debug("Reading config from '%s'." % self.configFile)
        self.channelMap = {}
        try:
            self.channelMap = json.loads(open(self.configFile, 'r').read())
        except:
            return self.channelMap
        for key in self.channelMap.keys():
            channelElement = self.document.createElement('channel')
            channelElement.setAttribute('id', '%s.telkku.com' % key)
            nameElement = self.document.createElement('display-name')
            nameElementText = self.document.createTextNode(self.channelMap[key])
            nameElement.appendChild(nameElementText)
            channelElement.appendChild(nameElement)
            self.tvNode.appendChild(channelElement)
        self.channelCount = len(self.channelMap.keys())
        self.currentStep = 0
        return self.channelMap

    # Save the channel config.
    def saveChannelConfig(self, channelMap):
        try:
            os.makedirs(os.path.split(self.configFile)[0])
        except:
            pass
        open(self.configFile, 'w').write(json.dumps(channelMap))
        logging.debug("Config file saved.")

    # Check command line.
    def checkCommandLine(self):
        parser = optparse.OptionParser()
        parser.add_option("--configure", dest = "configure",
                          help = "Run configuration.", action = "store_true", default = False)
        parser.add_option("--config-file", dest = "config_file",
                          help = "Use the specified config file.", metavar = "FILE", type = 'string', default = False)
        parser.add_option("--description", dest = "description",
                          help = "Show description line.", action = "store_true", default = False)
        parser.add_option("--capabilities", dest = "capabilities",
                          help = "Show capabilities information.", action = "store_true", default = False)
        parser.add_option("--quiet", dest = "quiet",
                          help = "Run silent.", action = "store_true", default = False)
        parser.add_option("--output", dest = "output",
                          help = "Specify the output file.", metavar = "FILE", default = '-', type = 'string')
        parser.add_option("--days", dest = "days",
                          help = "Specify the number of days to grab.", metavar = "X", default = 14, type = 'int')
        parser.add_option("--version", dest = "version",
                          help = "Show version number and exit.", action = 'store_true', default = False)
        parser.add_option("--offset", dest = "offset",
                          help = "Specify the number of days to add to current date to start grabbing.", metavar = "X", default = 0, type = 'int')
        (self.options, args) = parser.parse_args()
        # Check options.
        if self.options.version:
            print("v2011-04-07_21-56")
            sys.exit(0)
        if self.options.configure:
            try:
                os.remove(self.configFile)
            except:
                # Not an error.
                pass
        if self.options.config_file:
            self.configFile = self.options.config_file
        if self.options.description:
            print "Finland (m.telkku.com)"
            sys.stdout.flush()
            sys.exit(0)
        if self.options.capabilities:
            print "baseline\nmanualconfig"
            sys.exit(0)
        if self.options.quiet:
            logging.basicConfig(format = "%(relativeCreated)6d: %(message)s", level = logging.CRITICAL)
        else:
            logging.basicConfig(format = "%(relativeCreated)6d: %(message)s", level = logging.DEBUG)

    # Print the tree.
    def printTree(self):
        writer = None
        if self.options.output == '-':
            print self.document.toprettyxml('  ', '\n', 'utf-8')
        else:
            writer = codecs.open(self.options.output, "w", encoding = "utf-8")
            self.document.writexml(writer, indent = "  ", addindent = "  ", newl = "\n", encoding = "utf-8")

# Entry point.
if __name__ == "__main__":
    grabber = Grabber()
    # Check options.
    grabber.checkCommandLine()

    # Try loading channels from the config file.
    print "<!--"
    channelMap = grabber.loadChannelConfig()
    if len(list(channelMap.keys())) == 0:
        logging.info("You haven't got a channel configuration file. I'm going to try to create one for you.")
        # Get program groups' URLs.
        urls = grabber.getProgramGroupUrls()
        # Process each program group.
        for url in urls:
            tmpMap = grabber.getChannelUrls(url)
            for key in list(tmpMap.keys()):
                channelMap[key] = tmpMap[key]
        # Save channel config.
        grabber.saveChannelConfig(channelMap)
        print " />"
        print >> sys.stderr, "Now it's your turn. Edit '%s' and remove channels (lines in the file) that you don't want to grab." % grabber.configFile
    else:
        # Process program guide for each channel.
        startDate = datetime.datetime(1, 1, 1).today() + datetime.timedelta(grabber.options.offset)
        for key in list(channelMap.keys()):
            for dayCounter in range(0, grabber.options.days):
                fetchDate = startDate + datetime.timedelta(dayCounter)
                grabber.getProgramGuide(key, fetchDate.strftime("%Y%m%d"))
        print(" />")
        grabber.printTree()
